package tests;

import us.versus.them.intersect.Intersect;
import us.versus.them.intersect.Point;

typedef TriangleTest = {
	var non_hit:Int;
	var cul_hit:Int;
	var t:Float;

	var v0  :Point;
	var v1  :Point;
	var v2  :Point;
	var src :Point;
	var dir :Point;
}

class AppTest extends haxe.unit.TestCase {

	public function testRandomTriangle() {
		var max = 10;
		var hits = 0;
		for ( i in 0 ... max ) {
			if ( randomTriangle() ) {
				hits++;
			}
		}
		trace( hits + '/' + max + ' random tests produced hits' );
	}

	public function randomTriangle() {
		var hit = false;

		var vert0 = Intersect.represent( r(), r(), r() );
		var vert1 = Intersect.represent( r(), r(), r() );
		var vert2 = Intersect.represent( r(), r(), r() );
		var src = Intersect.represent( r(), r(), r() );
		var dir = Intersect.represent( r(), r(), r() );

		var cul = Intersect.threePt();
		var non = Intersect.threePt();

		var non_hit = Intersect.intersect_triangle_non_culling( src , dir , vert0 , vert1 , vert2 , non );
		var cul_hit = Intersect.intersect_triangle( src , dir , vert0 , vert1 , vert2 , cul );

		if ( 1 == non_hit ) {
			if ( 1 == cul_hit ) {
				assertEquals( cul.x, non.x );
				hit = true;
			}
		} else {
			// if non_culling didn't hit, culling could not have either:
			assertEquals( 0, cul_hit );
		}

		if ( !true ) {
			if ( hit || Math.random() < 0.2 ) {
				// generate some test data from the straight up port
				trace( 
					'<hit non_hit="' 
					+ non_hit 
					+ '" cul_hit="' 
					+ cul_hit 
					+ '" t="' 
					+ cul.x + '">' 
				);
				trace( '<v0 ' + toString( vert0 ) + '/>' );
				trace( '<v1 ' + toString( vert1 ) + '/>' );
				trace( '<v2 ' + toString( vert2 ) + '/>' );
				trace( '<src ' + toString( src ) + '/>' );
				trace( '<dir ' + toString( dir ) + '/>' );
				trace( '</hit>' );
			}
		}
		return hit;
	}

	public function r() {
		return Math.random();
	}

	public function toString( v:Point ) {
		return v.toString();
	}

	public function testCanned() {
		var count = 0;
		var tests = loadTriangles();

		var startTime = Date.now();
		for ( i in 0 ... 200 ) {
			for ( test in tests ) {
				verifyTriangle( test );
				count++;
			}
		}
		var stopTime = Date.now();
		var diff = ( stopTime.getTime() - startTime.getTime() ) / 1000.0;

		trace( count + ' tests / ' + diff + 's = ' + ( count / diff ) + ' tests/s' );
	}

	public function loadTriangles() {
		trace( 'loading xml test data' );
		var xml = Xml.parse(
			neko.io.File.getContent( 'tests/data/triangles.xml' )
		);
		var tests:Array<TriangleTest> = new Array< TriangleTest >();
		for ( triangle in xml.elementsNamed( 'triangle' ) ) {
			for ( hit in triangle.elementsNamed( 'hit' ) ) {
				var test:TriangleTest = {
					  non_hit : Std.parseInt( hit.get( 'non_hit' ) )
					, cul_hit : Std.parseInt( hit.get( 'cul_hit' ) )
					, t : Std.parseFloat( hit.get( 't' ) )
					, v0  : pt( hit.elementsNamed( 'v0' ).next() )
					, v1  : pt( hit.elementsNamed( 'v1' ).next() )
					, v2  : pt( hit.elementsNamed( 'v2' ).next() )
					, src : pt( hit.elementsNamed( 'src' ).next() )
					, dir : pt( hit.elementsNamed( 'dir' ).next() )
				}
				tests.push( test );

				verifyTriangle( test );
			}
		}
		trace( 'loaded xml test data' );
		return tests;
	}

	public function verifyTriangle( test:TriangleTest ) {
		verify( test.non_hit, test.cul_hit , test.t, test.v0 , test.v1 , test.v2 , test.src , test.dir );
	}

	public function verify( x_non_hit, x_cul_hit , x_t:Float, v0 , v1 , v2 , src , dir ) {
		var cul = Intersect.threePt();
		var non = Intersect.threePt();

		var non_hit = Intersect.intersect_triangle_non_culling( src , dir , v0 , v1 , v2 , non );
		var cul_hit = Intersect.intersect_triangle( src , dir , v0 , v1 , v2 , cul );

		assertEquals( x_non_hit, non_hit );
		assertEquals( x_cul_hit, cul_hit );
		if ( Math.abs( cul.x - x_t ) > Intersect.EPSILON ) {
			assertEquals( x_t, cul.x );
		}
	}

	public function pt( x:Xml ) {
		return Intersect.represent( 
			  Std.parseFloat( x.get( 'x' ) )
			, Std.parseFloat( x.get( 'y' ) )
			, Std.parseFloat( x.get( 'z' ) )
		);	
	}

}
